/*
/*
 * Copyright (C) 2010 - 2012 Jenia Software.
 *
 * This file is part of Sinekarta
 *
 * Sinekarta is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Sinekarta is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */
package org.sinekartads.share.webscripts;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.sinekartads.dto.DTOFormatter;
import org.sinekartads.dto.share.WizardDTO;
import org.sinekartads.dto.share.WizardDTO.ActionErrorDTO;
import org.sinekartads.dto.share.WizardDTO.FieldErrorDTO;
import org.sinekartads.dto.share.WizardDTO.WizardStepDTO;
import org.sinekartads.dto.tools.DTOConverter;
import org.sinekartads.share.ShareConfiguration;
import org.sinekartads.share.util.AlfrescoException;
import org.sinekartads.util.TemplateUtils;
import org.springframework.core.GenericTypeResolver;
import org.springframework.extensions.surf.util.I18NUtil;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptRequest;

public abstract class WSController<DTO extends WizardDTO> extends BaseWS {
	
	final Logger tracer = Logger.getLogger(getClass());
	
	// -----
	// --- Communication with the form
	// -

	/**
	 * Unique ID of the HTML page hosting the JS controller, automatically generated by Alfresco
	 * Share and continuosly exchanged between the two controllers without being ever modified.<br/>
	 * Used both as input and output parameter in order to allow a standard widgets id generation 
	 * policy and grant in that way the JS controller to be able to retrieve them.
	 */
	public static final String IO_HTMLID = "htmlid";
	
	/**
	 * Back-page return URL. When the documentLibrary performs the first wizard webScript call, it
	 * takes the current URL and send it to the WS controller. This value will be then continuosly
	 * exchanged between the two controllers without being ever modified.<br/>
	 * Used both as input and output parameter in order to allow the JS controller to return to the
	 * calling page when the wizard execution succeed or has been aborted by the user.
	 */
	public static final String IO_BACKURL = "backUrl";
	
	public static final String RC_SUCCESS = "SUCCESS";
	public static final String RC_ERROR = "ERROR";
	
	public static final String IO_WIZARDJSON	= "wizardDataJSON";
	
	/**
	 * Wizard data transport object. <br/>
	 * Received as JSON input from the jsController
	 */
	public static final String OUT_WIZARDDATA	= "wscWizardData";
	
	
	// -----
	// --- Required bundle resources
	// -

	// LABELS - dictionary of the system labels, have to be declared into the bundle in any format
	public static final String MANDATORY  	  = "skds.error.mandatory";
	
	// ERRORS - dictionary of the known errors, have to be declared into the bundle
	//		Any error into sineKarta platform is expected to appear with the form error.<type>, such as
	//				- error.generic = Errore generico!
	//				  [...]
	public static final String GENERIC_ERROR  = "error.generic";
	
	// RESULT_CODES - format for the Alfresco communication result codes
	// 		Any result code needs a translation into the bundle with the format resultCode.<type>, such as
	//				- resultCode.SUCCESS = OK
	//				- resultCode.INTERNAL_CLIENT_ERROR = Errore generico lato Share.
	//				  [...]
	public static final String RESULT_CODE 	  = "resultCode.%s";

	
	
	// -----
	// --- Shared tools and data
	// -
	
	protected final ShareConfiguration conf = ShareConfiguration.getInstance();
	protected final DTOConverter converter = DTOConverter.getInstance();
	

	
	// -----
	// --- WizardDTO class recognization
	// -
	
	private final Class<? extends WizardDTO> dtoClass;
	
	@SuppressWarnings("unchecked")
	protected WSController ( ) {
		Class<?>[] typeArgs = GenericTypeResolver.resolveTypeArguments(getClass(), WSController.class);
		Class<WizardDTO> clazz = null;
		for (Class<?> typeArg : typeArgs) {
			if (WizardDTO.class.isAssignableFrom(typeArg)) {
				clazz = (Class<WizardDTO>) typeArg;
			}
		}
		if(clazz == null) {
			throw new RuntimeException("unable to recognize the dtoClass");
		}
		dtoClass = clazz;
	}

	
	
	// ----- 
	// --- WebScript protocol
	// -
	
	@SuppressWarnings("unchecked")
	@Override
	public Map<String, Object> executeImpl (
			WebScriptRequest req, 
			Status status, 
			Cache cache ) {
		
		Map<String, Object> model = new HashMap<String, Object>();
		String jscWizardData = getParameter ( req, IO_WIZARDJSON );
		String htmlid 		 = getParameter ( req, IO_HTMLID );
		DTO    wizardData	 = null;

		try {
			// Retrieve the WizardDTO instance
			wizardData = (DTO) TemplateUtils.Encoding.deserializeJSON ( dtoClass, jscWizardData );
			wizardData.setActionErrors ( new ActionErrorDTO[0] );
			wizardData.setFieldErrors ( new FieldErrorDTO[0] );

			// Process the wizardData
			processData ( wizardData );
		} catch(Exception e) {
			processError ( wizardData, e );
		} finally {
			WizardStep[] steps = getWizardSteps();
			WizardStepDTO[] wizardSteps = new WizardStepDTO[steps.length];
			for ( int i=0; i<steps.length; i++ ) {
				wizardSteps[i] = toWizardStepDTO(steps[i]);
			}
			wizardData.setCurrentStep ( Integer.toString(currentStep()) );
			wizardData.setWizardSteps ( wizardSteps );
			wizardData.setWizardForms ( getWizardForms() );
			
			// Send the htmlid back only if it has been previously populated by Alfresco share
			if ( StringUtils.isNotBlank(htmlid) ) {
				model.put ( IO_HTMLID, htmlid );
			}
			
			// Send the other output parameters to the JS controller
			model.put ( IO_WIZARDJSON,     wizardData.toJSON() );
			model.put ( OUT_WIZARDDATA,    wizardData );
		}
		return model;
	}
	
	protected void processError ( DTO wizardData, String errorMessage ) {
		processError ( wizardData, errorMessage, null );
	}
	
	protected void processError ( DTO wizardData, Exception errorCause ) {
		processError ( wizardData, null, errorCause );
	}
	
	protected void processError ( DTO wizardData, String errorMessage, Exception errorCause ) {
		if ( StringUtils.isBlank(errorMessage) ) {
			Throwable cause = errorCause;
			do {
				errorMessage = errorCause.getMessage(); 
				cause = cause.getCause();
			} while ( cause != null && StringUtils.isBlank(errorMessage) );
			if ( StringUtils.isNotBlank(errorMessage) ) {
				if ( errorMessage.startsWith("skds.error.") ) {
					errorMessage = getMessage(errorMessage);
				}
			} else {
				errorMessage = errorCause.getClass().getName();
			} 
		}
		
		wizardData.addActionError ( errorMessage, errorCause );
		tracer.error(errorMessage, errorCause);
	}

	protected abstract void processData ( 
			DTO wizardDto ) 
					throws AlfrescoException ;
	
	public static class WizardStep {
		
		public WizardStep ( String name, String form ) {
			this.name = name;
			this.form = form;
		}
		
		public boolean equals ( WizardStep step ) {
			return StringUtils.equals ( name, step.name );
		}
		
		final String name;
		final String form; 
	}

	protected abstract String[] getWizardForms ( ) ;
	
	protected abstract WizardStep[] getWizardSteps ( ) ;
	
	protected abstract int currentStep ( ) ;
	
	private WizardStepDTO toWizardStepDTO ( WizardStep wizardStep ) {
		WizardStepDTO dto = new WizardStepDTO();
		dto.setName(wizardStep.name);
		dto.setForm(wizardStep.form);
		return dto;
	}
	
	
	
	// -----
	// --- DTO format
	// -
	
	private Map<Locale, DTOFormatter> dtoFormatters = new HashMap<Locale, DTOFormatter>();
	
	protected DTOFormatter getDtoFormatter() {
		Locale locale = I18NUtil.getLocale();
		DTOFormatter dtoFormatter = dtoFormatters.get(locale);
		if(dtoFormatter == null) {
			dtoFormatter = new DTOFormatter(getResources());
			dtoFormatter.loadFormats(ResourceBundle.getBundle("alfresco/messages/skds-commons.properties"));
			dtoFormatters.put(locale, dtoFormatter);
		}
		return dtoFormatter;
	}
	
	
	
	// -----
	// --- Error management
	// -
	
	protected void addFieldError( DTO dto, String field, String error ) {
		dto.addFieldError(field, error);
	}
}